#include <InformationManager.h>
#include "Util.h"
#include "../BasicAIModule/Source/BasicAIModule.h"
#include "math.h"
using namespace std;
using namespace BWAPI;

InformationManager* TheInformationManager = NULL;

InformationManager* InformationManager::create()
{
  if (TheInformationManager) return TheInformationManager;
  return new InformationManager();
}
void InformationManager::destroy()
{
  if (TheInformationManager)
    delete TheInformationManager;
}
InformationManager::InformationManager()
{
  TheInformationManager = this;
  buildTime[Broodwar->enemy()->getRace().getCenter()]=0;
  buildTime[Broodwar->enemy()->getRace().getWorker()]=0;
  if (Broodwar->enemy()->getRace()==Races::Zerg)
  {
    buildTime[UnitTypes::Zerg_Larva]=0;
    buildTime[UnitTypes::Zerg_Overlord]=0;
  }
  startLocationCouldContainEnemy = BWTA::getStartLocations();
  this->healthyMutalisksCount = 0;
}
InformationManager::~InformationManager()
{
  TheInformationManager = NULL;
}


int InformationManager::getHealthyMutasCount (){
  //return this->healthyMutalisksCount; TODO: incrementally calculate this!!!
  int count = 0;
  for each (Unit* unit in  SelectAll(UnitTypes::Zerg_Mutalisk)){
    if (TheDefenseManager->isHealed(unit))
      count++;
  }
  return count;
}

void InformationManager::onUnitDiscover(Unit* unit)
{
  
  savedData[unit].exists = true;
  int test2=0;
  if (!Broodwar->self()->isEnemy(unit->getPlayer()) )  { 
   
    return;
  }
  enemyUnitList[unit->getType()].insert(unit);
  visibleEnemyUnits.insert(unit);
  int time=Broodwar->getFrameCount();
  UnitType type=unit->getType();
  updateBuildTime(type,time-type.buildTime());
  


  // Only proceed if we have BWTA information
  if (!BasicAIModule::analyzed)
    return;

  // updates new/saved enemy units close to our bases


  if (unit->getType().isBuilding())
  {
    BWTA::Region* r=BWTA::getRegion(unit->getTilePosition());
    if (r->getBaseLocations().size()==1)
    {
      BWTA::BaseLocation* b = *(r->getBaseLocations().begin());
      this->setBaseOccupied(b);
      scoutedAnEnemyBase = true;
    }
  }
  if (unit->getType().isResourceDepot())
  {
    BWTA::BaseLocation* b=BWTA::getNearestBaseLocation(unit->getTilePosition());
    this->setBaseOccupied(b);
    enemyBaseCenters[b]=unit;
    scoutedAnEnemyBase = true;

  }

  // update lastScouted if applicable
  if (unit->getType().isBuilding())
  {
    BWTA::BaseLocation* closest = BWTA::getNearestBaseLocation(unit->getPosition());
    lastScouted[closest] = BWAPI::Broodwar->getFrameCount();
  }
}
void InformationManager::onUnitEvade(Unit* unit)
{
  savedData[unit].player=unit->getPlayer();
  savedData[unit].type=unit->getType();
  savedData[unit].position=unit->getPosition();
  savedData[unit].lastSeenTime=Broodwar->getFrameCount();

  // this enemy unit is no longer visible
  if (Broodwar->self()->isEnemy(unit->getPlayer()))
    visibleEnemyUnits.erase(unit);
}
void InformationManager::onUnitDestroy(Unit* unit)
{
  this->onUnitEvade(unit);
  
  savedData[unit].exists=false;
  /**
  if (unit->getPlayer() == Broodwar->self() )
    if (unit->getType() == UnitTypes::Zerg_Mutalisk)
      healthyMutalisksCount--;
*/
  if (!Broodwar->self()->isEnemy(unit->getPlayer())) {
    return;
  }
   
  enemyUnitList[unit->getType()].erase(unit);
  visibleEnemyUnits.erase(unit);
  
  // Only proceed if we have BWTA information
  if (!BasicAIModule::analyzed)
    return;

  if (unit->getType().isBuilding())
  {
    BWTA::BaseLocation* b=BWTA::getNearestBaseLocation(unit->getTilePosition());
    UnitGroup surroundingEnemyBuildings = this->getAllKnownEnemy()(isBuilding).inRegion(b->getRegion());
    if (surroundingEnemyBuildings.size() > 0) 
      return; // still got enemy there.

    // Should be clear
    this->setBaseEmpty(b);
  }
}

Player* InformationManager::getPlayer(Unit* unit) const
{
  if (unit->exists())
    return unit->getPlayer();
  map<Unit*,UnitData>::const_iterator i=savedData.find(unit);
  if (i==savedData.end())
    return NULL;
  return (*i).second.player;
}

UnitType InformationManager::getType(Unit* unit) const
{
  if (unit->exists())
    return unit->getType();
  map<Unit*,UnitData>::const_iterator i=savedData.find(unit);
  if (i==savedData.end())
    return UnitTypes::None;
  return (*i).second.type;
}

Position InformationManager::getLastPosition(Unit* unit) const
{
  if (unit->exists())
    return unit->getPosition();
  map<Unit*,UnitData>::const_iterator i=savedData.find(unit);
  if (i==savedData.end())
    return Positions::None;
  return (*i).second.position;
}

int InformationManager::getLastSeenTime(Unit* unit) const
{
  if (unit->exists())
    return Broodwar->getFrameCount();
  map<Unit*,UnitData>::const_iterator i=savedData.find(unit);
  if (i==savedData.end())
    return -1;
  return (*i).second.lastSeenTime;
}


bool InformationManager::isCloseToBase(std::set<BWAPI::Unit*> units, BWTA::BaseLocation* baseLoc){
  BWAPI::Position groupPos = getUnitGroupPosition(units);
  /**
  // some kind of scaling, so bigger bases shall require larger reaction radii
  double regionPerimeter = baseLoc->getRegion()->getPolygon().getPerimeter();
  //double distThreshold = ( log10 ( regionPerimeter) * 25 );
  double distThreshold = ( regionPerimeter / 10 );
  BWAPI::Position closestPoint = baseLoc->getRegion()->getPolygon().getNearestPoint(groupPos);

  bool insideAlready = baseLoc->getRegion()->getPolygon().isInside(groupPos);
  */
  double distThreshold = 1000;

  return (groupPos.getDistance(baseLoc->getPosition()) < distThreshold );

}

// checks if the enemy has established a base here!
bool InformationManager::isEnemyBase (BWTA::BaseLocation* baseLoc) {
  return enemyBases.find(baseLoc) != enemyBases.end();
}

bool InformationManager::isCloseToBase(BWAPI::Unit* unit, BWTA::BaseLocation* baseLoc){
  BWAPI::Position pos = unit->getPosition();
  // some kind of scaling, so bigger bases shall require larger reaction radii
  /**double regionPerimeter = baseLoc->getRegion()->getPolygon().getPerimeter();
  //double distThreshold = ( log10 ( regionPerimeter) * 25 );
  double distThreshold = ( regionPerimeter / 10 );
  BWAPI::Position closestPoint = baseLoc->getRegion()->getPolygon().getNearestPoint(pos);

  bool insideAlready = baseLoc->getRegion()->getPolygon().isInside(pos);
  */
  double distThreshold = 1000;

  return (pos.getDistance(baseLoc->getPosition()) < distThreshold );

}

UnitGroup InformationManager::getAllKnownEnemy()
{
  UnitGroup group;
  for each(std::pair<UnitType,std::set<Unit*>> entry in enemyUnitList) {
    if (entry.first.isRefinery()) {
      // check to make sure these are not neutral, they might slip in after you kill one
      for (std::set<Unit*>::iterator i=entry.second.begin(); i != entry.second.end();) {
        if (!(*i)->getPlayer()->isEnemy(Broodwar->self()))
          entry.second.erase(i++);
        else 
          ++i;
      }
    }

    group += UnitGroup::getUnitGroup(entry.second);
  }
  return group;
}

// TODO: make it actually work!
BWAPI::Position InformationManager::getUnitGroupPosition(std::set<BWAPI::Unit*> units) {
  BWAPI::Unit* firstUnit = *units.begin();
  return firstUnit->getPosition();
}

// Goes through list of existing units that are close to home bases, and 
//  groups them into clusters and chooses a point to represent that cluster.

std::list<InformationManager::Threat> InformationManager::getCurrentActiveThreats(){
  
  std::list<InformationManager::Threat> results;
  if (visibleEnemyUnits.size() == 0) return results;

  std::set<BWAPI::Unit*>::iterator iter;

  for ( iter = this->visibleEnemyUnits.begin(); iter != this->visibleEnemyUnits.end();){
       // check if enemy is inside one of our regions?
    BWAPI::Unit* enemyUnit = *iter;

    // TONG: THIS IS A FUNKY BUG -- probably from BWAPI itself
    // If the map has an enemy Refinery that was created using the Campaign Map editor,
    // then once the Refinery is detroyed, the pointer to that Refinery points to the
    // Geyser underneath instead (which is still correctly owned by Neutral.)
    if (enemyUnit->getType() == BWAPI::UnitTypes::Resource_Vespene_Geyser){
      visibleEnemyUnits.erase(iter++);
      continue; // this is awkward...
    }
    BWAPI::Position pos = enemyUnit->getPosition();
    std::set<Base*>::iterator baseIter;
    std::set<Base*> allMyBases = TheBaseManager->getAllBases();

    for (baseIter = allMyBases.begin(); baseIter != allMyBases.end(); baseIter++){
      BWTA::BaseLocation* baseLocation = (*baseIter)->getBaseLocation();
      
      
      if ( this->isCloseToBase(enemyUnit, baseLocation)) { // enemy is inside one of our regions!
        // TODO: decide how to group units together in a set before inserting them
        // into a threat. For now, each unit is by itself.
        std::set<BWAPI::Unit*> units;
        units.insert(enemyUnit);
        InformationManager::Threat threat = InformationManager::Threat(units, pos);
        results.push_back(threat);
        break; // skip to the next unit, we don't wanna double count.
      }

    }
    iter++;
    
  }
  return results;
}


bool InformationManager::exists(Unit* unit) const
{
  if (unit->exists())
    return true;
  map<Unit*,UnitData>::const_iterator i=savedData.find(unit);
  if (i==savedData.end())
    return false;
  return (*i).second.exists;
}

bool InformationManager::enemyHasBuilt(UnitType type) const
{
  return (buildTime.find(type)!=buildTime.end());
}

int InformationManager::getBuildTime(UnitType type) const
{
  map<UnitType, int>::const_iterator i=buildTime.find(type);
  if (i==buildTime.end())
    return -1;
  return i->second;
}

const set<BWTA::BaseLocation*>& InformationManager::getEnemyBases() const
{
  return this->enemyBases;
}

// checks to see if there is any enemy unit which can damage the given unit
bool InformationManager::hasThreateningEnemy(BWAPI::Unit* unit) const {
  for each (BWAPI::Unit* entity in SelectAllEnemy(UnitTypes::Men) ) {
    double entSight = entity->getType().sightRange();
    
    BWAPI::WeaponType weapon = DefenseManager::getAppropriateWeapon(entity, unit);
    double entWeaponRange = weapon.maxRange();
    
    double maxInteractionRadius = (entSight<entWeaponRange)? entWeaponRange : entSight;
    if (maxInteractionRadius > unit->getPosition().getDistance(entity->getPosition() ) )
      return true;
  }
  return false;
}

void InformationManager::setBaseOccupied(BWTA::BaseLocation* base)
{
  enemyBases.insert(base);

  // if we have flagged this base, we need to abandon taking it
  // the construction manager will check before it builds
  if (TheBaseManager->getBase(base)) {
    TheBaseManager->removeBase(base);
  }
}

void InformationManager::setBaseEmpty(BWTA::BaseLocation* base)
{
  this->startLocationCouldContainEnemy.erase(base);
  this->enemyBases.erase(base);
  this->enemyBaseCenters.erase(base);
  if (startLocationCouldContainEnemy.size()==1)
  {
    enemyBases.insert(*startLocationCouldContainEnemy.begin());
    scoutedAnEnemyBase = true;
  }
}

void InformationManager::updateBuildTime(UnitType type, int time)
{
  map<UnitType, int>::iterator i=buildTime.find(type);
  if (i!=buildTime.end() && (i->second<=time || i->second==0)) return;
  buildTime[type]=time;
  if (time<0) return;
  for(map< UnitType,int>::const_iterator i=type.requiredUnits().begin();i!=type.requiredUnits().end();i++)
  {
    updateBuildTime(i->first,time-i->first.buildTime());
  }
}

int InformationManager::getUnitTypeCount(BWAPI::UnitType type)
{
  return enemyUnitList[type].size();
}

int InformationManager::getLastScouted(BWTA::BaseLocation* base)
{
  return lastScouted[base];
}

void InformationManager::setLastScouted(BWTA::BaseLocation* base, int time)
{
  if (base)
    lastScouted[base] = time;
}

void InformationManager::addItemOrder(std::string name)
{
  itemOrders[name]++;
}

void InformationManager::removeItemOrder(std::string name)
{
  itemOrders[name]--;
}

InformationManager::UnitData::UnitData()
{
  position     = Positions::Unknown;
  type         = UnitTypes::Unknown;
  player       = NULL;
  lastSeenTime = -1;
  exists       = false;
}
